package eva

import (
	"testing"

	"gitcode.com/deyiyangyang/gascheme/ast"
	"gitcode.com/deyiyangyang/gascheme/lex"
	"gitcode.com/deyiyangyang/gascheme/obj"
	"gitcode.com/deyiyangyang/gascheme/par"
)

// TestLambdaCreation tests the creation of lambda expressions
func TestLambdaCreation(t *testing.T) {
	// Create a lambda expression: (lambda (x) (+ x 1))
	input := "(lambda (x) (+ x 1))"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if the result is a Procedure object
	proc, ok := result.(*obj.Procedure)
	if !ok {
		t.Fatalf("Eval didn't return Procedure. Got %T", result)
	}

	// Check procedure properties
	if proc.Name != "lambda" {
		t.Errorf("Procedure has wrong name. Got %s, want 'lambda'", proc.Name)
	}

	if len(proc.Params) != 1 || proc.Params[0] != "x" {
		t.Errorf("Procedure has wrong parameters. Got %v, want ['x']", proc.Params)
	}

	if len(proc.Body) != 1 {
		t.Errorf("Procedure has wrong body length. Got %d, want 1", len(proc.Body))
	}
}

// TestLambdaApplication tests applying lambda expressions
func TestLambdaApplication(t *testing.T) {
	// Create a lambda application: ((lambda (x) (+ x 1)) 5)
	input := "((lambda (x) (+ x 1)) 5)"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if the result is an Integer object with value 6
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 6 {
		t.Errorf("Eval returned wrong value. Got %d, want 6", integer.Value)
	}
}

// TestLambdaWithMultipleParameters tests lambda expressions with multiple parameters
func TestLambdaWithMultipleParameters(t *testing.T) {
	// Create a lambda application: ((lambda (x y) (+ x y)) 3 4)
	input := "((lambda (x y) (+ x y)) 3 4)"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if the result is an Integer object with value 7
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 7 {
		t.Errorf("Eval returned wrong value. Got %d, want 7", integer.Value)
	}
}

// TestNestedLambda tests nested lambda expressions
func TestNestedLambda(t *testing.T) {
	// Create a nested lambda application: ((lambda (x) ((lambda (y) (+ x y)) 2)) 3)
	input := "((lambda (x) ((lambda (y) (+ x y)) 2)) 3)"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if the result is an Integer object with value 5
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 5 {
		t.Errorf("Eval returned wrong value. Got %d, want 5", integer.Value)
	}
}

// TestLambdaClosure tests that lambda expressions properly capture their environment
func TestLambdaClosure(t *testing.T) {
	// First define a variable, then create a lambda that uses it
	// (define x 10)
	// (define add-x (lambda (y) (+ x y)))
	// (add-x 5)
	
	// Create the define expression for x
	defineXExpr := &ast.ListExpression{
		Elements: []ast.Expression{
			&ast.SymbolLiteral{Value: "define"},
			&ast.SymbolLiteral{Value: "x"},
			&ast.IntegerLiteral{Value: 10},
		},
	}

	// Create the define expression for add-x
	defineAddXInput := "(define add-x (lambda (y) (+ x y)))"
	l := lex.NewLexer(defineAddXInput)
	p := par.NewParser(l)
	defineAddXExpr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create the application expression
	applyInput := "(add-x 5)"
	l = lex.NewLexer(applyInput)
	p = par.NewParser(l)
	applyExpr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the define expressions
	defineXResult := Eval(defineXExpr, env)
	if isError(defineXResult) {
		t.Fatalf("Error defining x: %s", defineXResult.Inspect())
	}

	defineAddXResult := Eval(defineAddXExpr, env)
	if isError(defineAddXResult) {
		t.Fatalf("Error defining add-x: %s", defineAddXResult.Inspect())
	}

	// Evaluate the application
	result := Eval(applyExpr, env)

	// Check if the result is an Integer object with value 15
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 15 {
		t.Errorf("Eval returned wrong value. Got %d, want 15", integer.Value)
	}
}

// TestLambdaWithNoParameters tests lambda expressions with no parameters
func TestLambdaWithNoParameters(t *testing.T) {
	// Create a lambda application: ((lambda () 42))
	input := "((lambda () 42))"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if the result is an Integer object with value 42
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 42 {
		t.Errorf("Eval returned wrong value. Got %d, want 42", integer.Value)
	}
}

